/**
 *
 */
package de.cbf.cam.dsa.person.generator;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.cbf.cam.dsa.person.ActivatorPersonDSA;
import de.cbf.cam.dsa.person.extension.interfaces.AbstractNamedGenerator;
import de.cbf.cam.dsa.person.extension.interfaces.IRCPGenerator;
import de.cbf.cam.dsa.person.generator.exception.PersonGenerationException;
import de.cbf.cam.dsa.rcp.model.entities.CultureDSA;
import de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA;
import de.cbf.cam.dsa.rcp.model.entities.RaceDSA;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.IServiceDSARCPFinder;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.CultureNotFoundException;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.EntityNotFoundException;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.ProfessionNotFoundException;
import de.cbf.cam.dsa.services.interfaces.rcp_provider.exceptions.RaceNotFoundException;
import de.cbf.util.uuidheader.entities.EntityWeighted;
import de.cbf.util.uuidheader.entities.UUIDReference;
import de.cbf.util.uuidheader.entities.UUIDHeader;
import de.cbf.util.uuidheader.util.EntityWeightedList;

/**
 * @author salter
 *
 */
public class RCPGenerator extends AbstractNamedGenerator implements IRCPGenerator{
	private static final Log logger = LogFactory.getLog(RCPGenerator.class);

	protected IServiceDSARCPFinder 	rcpFinder;

	/**
	 *
	 */
	public RCPGenerator() {
		rcpFinder	= ActivatorPersonDSA.getDefault().getRCPFinder();
		setName("RCP Generator");
	}

	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.person.generator.IRCPGenerator#resolveEntity(de.cbf.util.uuidheader.entities.UUIDReference, java.lang.String)
	 */
	@Override
	public UUIDHeader resolveEntity(UUIDReference nameUUID,String provider) throws EntityNotFoundException{
		return rcpFinder.resolveEntity(nameUUID, provider);
	}

	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.person.generator.IRCPGenerator#provideRaceBy(de.cbf.cam.dsa.rcp.model.entities.CultureDSA, de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA)
	 */
	@Override
	public RaceDSA provideRaceBy(CultureDSA culture, ProfessionDSA profession,Map<String,Object> properties) throws PersonGenerationException {
		EntityWeightedList possibleRaces = new EntityWeightedList();
		try {
			possibleRaces.addAll(rcpFinder.findRacesBy(culture, profession));
			//consider existing races
			possibleRaces = considerExistings(possibleRaces,properties.get(PropertyConstants.PROPERTY_RACES_EXISTING));
			//choose chosenCulture from possible cultures
			EntityWeighted choice = possibleRaces.chooseEntityByWeight();
			if(choice != null ){
				if (choice.getEntity() instanceof RaceDSA) {
					RaceDSA race = (RaceDSA) choice.getEntity();
					logger.debug("Found race: "+race.getName()+"("+choice.getWeight()+")");
					return race;
				}else{
					String errorMsg = "possible racesList chose object that is not chosenRace but: "+choice.getClass()+"\n" +
							"Should not happen at all";
					throw new RuntimeException(errorMsg);
				}
			}else{
				logger.error("could not choose a weighted entity");
				return null;
			}
		} catch (RaceNotFoundException e) {
			throw new PersonGenerationException(e);
		}
	}



	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.person.generator.IRCPGenerator#provideCultureBy(de.cbf.cam.dsa.rcp.model.entities.RaceDSA, de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA)
	 */
	@Override
	public CultureDSA provideCultureBy(RaceDSA race, ProfessionDSA profession,Map<String,Object> properties)throws PersonGenerationException {
		EntityWeightedList possibleCultures = new EntityWeightedList();
		try {
			possibleCultures.addAll(rcpFinder.findCulturesBy(race, profession));
			//consider existing races
			possibleCultures = considerExistings(possibleCultures,properties.get(PropertyConstants.PROPERTY_CULTURES_EXISTING));
			//choose chosenCulture from possible cultures
			EntityWeighted choice = possibleCultures.chooseEntityByWeight();
			if(choice != null ){
				if (choice.getEntity() instanceof CultureDSA) {
					CultureDSA culture = (CultureDSA) choice.getEntity();
					logger.debug("Found culture: "+culture.getName()+"("+choice.getWeight()+")");
					return culture;
				}else{
					String errorMsg = "possible culturesList chose object that is not chosenCulture but: "+choice.getClass()+"\n" +
							"Should not happen at all";
					throw new RuntimeException(errorMsg);
				}
			}else{
				logger.error("could not choose a weighted entity");
				return null;
			}
		} catch (CultureNotFoundException e) {
			throw new PersonGenerationException(e);
		}
	}

	/* (non-Javadoc)
	 * @see de.cbf.cam.dsa.person.generator.IRCPGenerator#provideProfessionBy(de.cbf.cam.dsa.rcp.model.entities.RaceDSA, de.cbf.cam.dsa.rcp.model.entities.CultureDSA)
	 */
	@Override
	public ProfessionDSA provideProfessionBy(RaceDSA race, CultureDSA culture,Map<String,Object> properties) throws PersonGenerationException {
		EntityWeightedList possibleProfessions = new EntityWeightedList();
		try {
			possibleProfessions.addAll(rcpFinder.findProfessionsBy(race, culture));
			//consider existing races
			possibleProfessions = considerExistings(possibleProfessions,properties.get(PropertyConstants.PROPERTY_PROFESSIONS_EXISTING));
			//choose chosenCulture from possible cultures
			EntityWeighted choice = possibleProfessions.chooseEntityByWeight();
			if(choice != null ){
				if (choice.getEntity() instanceof ProfessionDSA) {
					ProfessionDSA profession = (ProfessionDSA) choice.getEntity();
					logger.debug("Found profession: "+profession.getName()+"("+choice.getWeight()+")");
					return profession;
				}else{
					String errorMsg = "possible professionsList chose object that is not chosenProfession but: "+choice.getClass()+"\n" +
							"Should not happen at all";
					throw new RuntimeException(errorMsg);
				}
			}else{
				logger.error("could not choose a weighted entity");
				return null;
			}
		} catch (ProfessionNotFoundException e) {
			throw new PersonGenerationException(e);
		}
	}

	protected EntityWeightedList considerExistings(EntityWeightedList possibleEntities, Object propertyValue ) {
		if (propertyValue instanceof Map) {
			Map<?,?> existingRaces = (Map<?,?>) propertyValue;
			Set<?> keys = existingRaces.keySet();
			//get sum of possible entities(for percentage calculation
			Double possibleEntitesSum = possibleEntities.getSum().doubleValue();
			//switch off everytime calc of sum and max on possible entities(performance)
			possibleEntities.setSumAndMaxCalculationActive(false);

			Double existingSum = calcExistingSum(existingRaces.values());

			for (Object keyObject : keys) {
				if (keyObject instanceof UUIDReference) {
					UUIDReference reference = (UUIDReference) keyObject;
					Object existingEntityCountObject = existingRaces.get(reference);
					if (existingEntityCountObject instanceof Integer) {
						Integer existingEntityCount = (Integer) existingEntityCountObject;
						for (EntityWeighted entityWeighted : possibleEntities) {
							//if uuids of referenced entity and weighted entity are the same -> same entity
							if(reference.getSavedUuid().toString().equals(entityWeighted.getEntity().getUuid().toString())){
								//adjust weights to value of missing percentage
								Double presumedPercentage = entityWeighted.getWeight().doubleValue()/possibleEntitesSum;//TODO somehow catch sum == 0
								Double existingPercentage = existingEntityCount.doubleValue()/existingSum;//TODO somehow catch sum == 0
								Double newWeight = presumedPercentage - existingPercentage;
								if(newWeight < 0){
									newWeight = 0D;
								}
								//add newWeight to list
								entityWeighted.setWeight(newWeight);
							}
						}
					}
				}
			}
			//switch back on and calculate sum and max(performance)
			possibleEntities.setSumAndMaxCalculationActive(true);
		}
		return possibleEntities;
	}

	protected Double calcExistingSum(Collection<?> values) {
		Double sum = 0D;
		for (Object object : values) {
			if (object instanceof Double) {
				Double doubleValue = (Double) object;
				sum += doubleValue;
			}
			if (object instanceof Integer) {
				Integer intValue = (Integer) object;
				sum += intValue.doubleValue();
			}
		}
		return sum;
	}


}
